﻿// Copyright (C) 2014  Patrick Maher

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;

namespace Paragon.Geometry

{
    public static class BvhAddressing
    {
        private static int[] _depthMask = new int[] { 0x38000000,
                                                     0x7000000,
                                                     0xE00000,
                                                     0x1C0000,
                                                     0x38000,
                                                     0x7000,
                                                     0xE00,
                                                     0x1C0,
                                                     0x38,
                                                     0x7 };
        private static int[] _zOrderLookup;
        private static int[] _depthTranslation = new int[] { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };

        private static int _unshiftedOctant;
        private static int _octant;


        static BvhAddressing()
        {
            GenerateLookup();
        }

        private static void GenerateLookup()
        {
            _zOrderLookup = new int[1024];
            
            for (int i = 0; i < 1024; i++)
            {
                _zOrderLookup[i] = SpreadBits(i);
            }
        }
        private static int SpreadBits(int coord)
        {
            int bits = coord;

            bits = (bits | (bits << 16)) & 0x030000ff;
            bits = (bits | (bits << 8)) & 0x0300f00f;
            bits = (bits | (bits << 4)) & 0x030c30c3;
            bits = (bits | (bits << 2)) & 0x09249249;

            return bits;
        }
        
        public static int OctantAtDepth(int address, int depth)
        {
            _unshiftedOctant = address & _depthMask[depth];

            _octant = _unshiftedOctant >> (_depthTranslation[depth] * 3);

            return _octant;
        }

        public static int Encode(int x, int y, int z)
        {
            return (_zOrderLookup[z] | _zOrderLookup[y] << 1 | _zOrderLookup[x] << 2);
        }

        public static int Encode(Vec3f v)
        {            
            //return (_zOrderLookup[ClampFloat(v.Z)] | _zOrderLookup[ClampFloat(v.Y)] << 1 | _zOrderLookup[ClampFloat(v.X)] << 2);
            if (v.X >= _zOrderLookup.Length || v.Y >= _zOrderLookup.Length || v.Z >= _zOrderLookup.Length)
            {
                return 0;
            }
            return (_zOrderLookup[(int)v.Z] | _zOrderLookup[(int)v.Y] << 1 | _zOrderLookup[(int)v.X] << 2);
        }

        public static int TreeDepthFromVolumeSize(int volumeSize)
        {
            int volume = volumeSize * volumeSize * volumeSize;
            int treeDepth = 0;

            while (volume > 1)
            {
                volume /= 8;
                treeDepth++;
            }

            return treeDepth;
        }

        public static int ClampFloat(float f)
        {
            //return Convert.ToInt32(Math.Round(f));
            return (int)f;
        }

        internal static void GetAddressStack(int address, int Depth, ref Stack<int> addressStack)
        {
            for (int i = 0; i < Depth; i++)
            {
                addressStack.Push(address & 0x7);
                address >>= 3;
            }
        }
    }
}